// Utils
import { createNamespace } from '../utils'
import { isHidden } from '../utils/dom/style'
import { preventDefault } from '../utils/dom/event'
import { doubleRaf } from '../utils/dom/raf'
import { range } from '../utils/format/number' // Mixins
import { addUnit } from '../utils/format/unit'

import { TouchMixin } from '../mixins/touch'
import { ParentMixin } from '../mixins/relation'
import { BindEventMixin } from '../mixins/bind-event'
import './style'
var _createNamespace = createNamespace('swipe')
var createComponent = _createNamespace[0]
var bem = _createNamespace[1]

export default createComponent({
  mixins: [
    TouchMixin,
    ParentMixin('vanSwipe'),
    BindEventMixin(function(bind, isBind) {
      bind(window, 'resize', this.resize, true)
      bind(window, 'orientationchange', this.resize, true)
      bind(window, 'visibilitychange', this.onVisibilityChange)

      if (isBind) {
        this.initialize()
      } else {
        this.clear()
      }
    })
  ],
  props: {
    width: [Number, String],
    height: [Number, String],
    autoplay: [Number, String],
    vertical: Boolean,
    lazyRender: Boolean,
    indicatorColor: String,
    indicatorNumber: {
      type: Boolean,
      default: false
    },
    ratio: Number,
    radius: {
      type: [String, Number],
      default: 0
    },
    gutter: {
      type: [String, Number],
      default: 0
    },
    loop: {
      type: Boolean,
      default: true
    },
    duration: {
      type: [Number, String],
      default: 500
    },
    touchable: {
      type: Boolean,
      default: true
    },
    initialSwipe: {
      type: [Number, String],
      default: 0
    },
    showIndicators: {
      type: Boolean,
      default: true
    },
    stopPropagation: {
      type: Boolean,
      default: true
    }
  },
  data: function data() {
    return {
      rect: null,
      offset: 0,
      active: 0,
      deltaX: 0,
      deltaY: 0,
      swiping: false,
      computedWidth: 0,
      computedHeight: 0
    }
  },
  watch: {
    children: function children() {
      this.initialize()
    },
    initialSwipe: function initialSwipe() {
      this.initialize()
    },
    autoplay: function autoplay(_autoplay) {
      if (_autoplay > 0) {
        this.autoPlay()
      } else {
        this.clear()
      }
    }
  },
  computed: {
    count: function count() {
      return this.children.length
    },
    maxCount: function maxCount() {
      return Math.ceil(Math.abs(this.minOffset) / this.size)
    },
    delta: function delta() {
      return this.vertical ? this.deltaY : this.deltaX
    },
    size: function size() {
      return this[this.vertical ? 'computedHeight' : 'computedWidth']
    },
    trackSize: function trackSize() {
      return this.count * this.size
    },
    activeIndicator: function activeIndicator() {
      return (this.active + this.count) % this.count
    },
    isCorrectDirection: function isCorrectDirection() {
      var expect = this.vertical ? 'vertical' : 'horizontal'
      return this.direction === expect
    },
    trackStyle: function trackStyle() {
      var style = {
        transitionDuration: (this.swiping ? 0 : this.duration) + 'ms',
        transform: 'translate' + (this.vertical ? 'Y' : 'X') + '(' + this.offset + 'px)'
      }

      if (this.size) {
        var mainAxis = this.vertical ? 'height' : 'width'
        var crossAxis = this.vertical ? 'width' : 'height'
        style[mainAxis] = this.trackSize + 'px'
        style[crossAxis] = this[crossAxis] ? this[crossAxis] + 'px' : ''
      }

      return style
    },
    indicatorStyle: function indicatorStyle() {
      var styles = []
      if (this.indicatorColor) {
        styles.push({
          color: this.indicatorColor,
          backgroundColor: this.indicatorColor
        })
      }
      return styles
    },
    minOffset: function minOffset() {
      return (this.vertical ? this.rect.height : this.rect.width) - this.size * this.count
    },
    swipeStyle() {
      const styles = {}
      if (!this.vertical) {
        const ratio = Math.abs(this.ratio)
        if (ratio !== 0) {
          styles['--scalebox-ratio'] = `${100 / ratio}%`
        }
        if (this.gutter) {
          styles['--swipe-item-gutter'] = addUnit(this.gutter)
        }
        if (this.radius) {
          styles['--swipe-item-radius'] = addUnit(this.radius)
        }
      }
      return styles
    }
  },
  mounted: function mounted() {
    this.bindTouchEvent(this.$refs.track)
  },
  methods: {
    // initialize swipe position
    initialize: function initialize(active) {
      if (active === void 0) {
        active = +this.initialSwipe
      }

      if (!this.$el || isHidden(this.$el)) {
        return
      }

      clearTimeout(this.timer)
      var rect = {
        width: this.$el.offsetWidth,
        height: this.$el.offsetHeight
      }
      this.rect = rect
      this.swiping = true
      this.active = active
      this.active = active
      this.computedWidth = +this.width || rect.width
      this.computedHeight = +this.height || rect.height
      this.offset = this.getTargetOffset(active)
      this.children.forEach(function(swipe) {
        swipe.offset = 0
      })
      this.autoPlay()
    },
    // @exposed-api
    resize: function resize() {
      this.initialize(this.activeIndicator)
    },
    onVisibilityChange: function onVisibilityChange() {
      if (document.hidden) {
        this.clear()
      } else {
        this.autoPlay()
      }
    },
    onTouchStart: function onTouchStart(event) {
      if (!this.touchable) return
      this.clear()
      this.touchStartTime = Date.now()
      this.touchStart(event)
      this.correctPosition()
    },
    onTouchMove: function onTouchMove(event) {
      if (!this.touchable || !this.swiping) return
      this.touchMove(event)

      if (this.isCorrectDirection) {
        preventDefault(event, this.stopPropagation)
        this.move({
          offset: this.delta
        })
      }
    },
    onTouchEnd: function onTouchEnd() {
      if (!this.touchable || !this.swiping) return
      var size = this.size
      var delta = this.delta
      var duration = Date.now() - this.touchStartTime
      var speed = delta / duration
      var shouldSwipe = Math.abs(speed) > 0.25 || Math.abs(delta) > size / 2

      if (shouldSwipe && this.isCorrectDirection) {
        var offset = this.vertical ? this.offsetY : this.offsetX
        var pace = 0

        if (this.loop) {
          pace = offset > 0 ? (delta > 0 ? -1 : 1) : 0
        } else {
          pace = -Math[delta > 0 ? 'ceil' : 'floor'](delta / size)
        }

        this.move({
          pace: pace,
          emitChange: true
        })
      } else if (delta) {
        this.move({
          pace: 0
        })
      }

      this.swiping = false
      this.autoPlay()
    },
    getTargetActive: function getTargetActive(pace) {
      var active = this.active
      var count = this.count
      var maxCount = this.maxCount

      if (pace) {
        if (this.loop) {
          return range(active + pace, -1, count)
        }

        return range(active + pace, 0, maxCount)
      }

      return active
    },
    getTargetOffset: function getTargetOffset(targetActive, offset) {
      if (offset === void 0) {
        offset = 0
      }

      var currentPosition = targetActive * this.size

      if (!this.loop) {
        currentPosition = Math.min(currentPosition, -this.minOffset)
      }

      var targetOffset = offset - currentPosition

      if (!this.loop) {
        targetOffset = range(targetOffset, this.minOffset, 0)
      }

      return targetOffset
    },
    move: function move(_ref) {
      var _ref$pace = _ref.pace
      var pace = _ref$pace === void 0 ? 0 : _ref$pace
      var _ref$offset = _ref.offset
      var offset = _ref$offset === void 0 ? 0 : _ref$offset
      var emitChange = _ref.emitChange
      var loop = this.loop
      var count = this.count
      var active = this.active
      var children = this.children
      var trackSize = this.trackSize
      var minOffset = this.minOffset

      if (count <= 1) {
        return
      }

      var targetActive = this.getTargetActive(pace)
      var targetOffset = this.getTargetOffset(targetActive, offset) // auto move first and last swipe in loop mode

      if (loop) {
        if (children[0] && targetOffset !== minOffset) {
          var outRightBound = targetOffset < minOffset
          children[0].offset = outRightBound ? trackSize : 0
        }

        if (children[count - 1] && targetOffset !== 0) {
          var outLeftBound = targetOffset > 0
          children[count - 1].offset = outLeftBound ? -trackSize : 0
        }
      }

      this.active = targetActive
      this.offset = targetOffset

      if (emitChange && targetActive !== active) {
        this.$emit('change', this.activeIndicator)
      }
    },
    // @exposed-api
    prev: function prev() {
      var _this = this

      this.correctPosition()
      this.resetTouchStatus()
      doubleRaf(function() {
        _this.swiping = false

        _this.move({
          pace: -1,
          emitChange: true
        })
      })
    },
    // @exposed-api
    next: function next() {
      var _this2 = this

      this.correctPosition()
      this.resetTouchStatus()
      doubleRaf(function() {
        _this2.swiping = false

        _this2.move({
          pace: 1,
          emitChange: true
        })
      })
    },
    // @exposed-api
    swipeTo: function swipeTo(index, options) {
      var _this3 = this

      if (options === void 0) {
        options = {}
      }

      this.correctPosition()
      this.resetTouchStatus()
      doubleRaf(function() {
        var targetIndex

        if (_this3.loop && index === _this3.count) {
          targetIndex = _this3.active === 0 ? 0 : index
        } else {
          targetIndex = index % _this3.count
        }

        if (options.immediate) {
          doubleRaf(function() {
            _this3.swiping = false
          })
        } else {
          _this3.swiping = false
        }

        _this3.move({
          pace: targetIndex - _this3.active,
          emitChange: true
        })
      })
    },
    correctPosition: function correctPosition() {
      this.swiping = true

      if (this.active <= -1) {
        this.move({
          pace: this.count
        })
      }

      if (this.active >= this.count) {
        this.move({
          pace: -this.count
        })
      }
    },
    clear: function clear() {
      clearTimeout(this.timer)
    },
    autoPlay: function autoPlay() {
      var _this4 = this

      var autoplay = this.autoplay

      if (autoplay > 0 && this.count > 1) {
        this.clear()
        this.timer = setTimeout(function() {
          _this4.next()

          _this4.autoPlay()
        }, autoplay)
      }
    },
    genIndicator: function genIndicator() {
      var _this5 = this

      var h = this.$createElement
      var count = this.count
      var activeIndicator = this.activeIndicator
      var slot = this.slots('indicator')

      if (slot) {
        return slot
      }

      if (this.showIndicators && count > 1) {
        return h(
          'div',
          {
            class: bem('indicators', {
              vertical: this.vertical
            })
          },
          [
            Array.apply(void 0, Array(count)).map(function(empty, index) {
              return h('i', {
                class: bem('indicator', {
                  active: index === activeIndicator
                }),
                style: index === activeIndicator ? _this5.indicatorStyle : null
              })
            })
          ]
        )
      }
    }
  },
  render: function render() {
    var h = arguments[0]
    return h(
      'div',
      {
        class: bem({
          scale: this.ratio,
          vertical: this.vertical
        }),
        style: this.swipeStyle
      },
      [
        h(
          'div',
          {
            ref: 'track',
            style: this.trackStyle,
            class: bem('track', {
              vertical: this.vertical
            })
          },
          [this.slots()]
        ),
        this.genIndicator()
      ]
    )
  }
})
